﻿/*
THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

Copyright (C) 1993 - 2000.  Microsoft Corporation.  All rights reserved.
*/

#ifdef WIN32

#include "os_service_windows.hh"
#include "../box/box_os.hh"
#include "box_argument_impl.hh"

#define MSG_FLAGS_SYS_CODE 1
#define MSG_FLAGS_ERROR MSG_FLAGS_SYS_CODE

/**
 * 服务容器主函数.
 */
extern int service_box_main(int argc, const char **argv);
/**
 * 服务状态.
 */
SERVICE_STATUS ssStatus;
/**
 * 服务状态句柄.
 */
SERVICE_STATUS_HANDLE sshStatusHandle;
/**
 * 错误码.
 */
DWORD dwErr = 0;
/**
 * 错误描述.
 */
TCHAR szErr[512] = {0};
/**
 * 服务名称.
 */
std::string service_name;

/**
 * 设置服务名称.
 *
 * \param name 服务名称
 */
void set_service_name(const std::string &name) { service_name = name; }
/**
 * 检查是否是服务.
 * 
 * \return 
 */
bool check_service();
bool is_windows_service() { return check_service(); }

/**
 * 启动为Windows服务.
 *
 * \return true或false
 */
bool start_as_service() {
  TCHAR szPath[512] = {0};
  // 注册服务入口函数
  SERVICE_TABLE_ENTRY dispatchTable[] = {
      {service_name.data(), (LPSERVICE_MAIN_FUNCTION)service_main_internal},
      {nullptr, nullptr}};
  // 启动线程运行主循环
  if (FALSE == ::StartServiceCtrlDispatcher(dispatchTable)) {
    GetLastErrorText(szErr, sizeof(szErr) - 1);
    std::string error("Start service control dispatcher failed:");
    error += szErr;
    AddToMessageLog(MSG_FLAGS_ERROR, error.c_str());
    return false;
  }
  return true;
}

//
//  FUNCTION: service_main
//
//  PURPOSE: To perform actual initialization of the service
//
//  PARAMETERS:
//    dwArgc   - number of command line arguments
//    lpszArgv - array of command line arguments
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//    This routine performs the service initialization and then calls
//    the user defined ServiceStart() routine to perform majority
//    of the work.
//
void WINAPI service_main_internal(DWORD dwArgc, LPTSTR *lpszArgv) {
  // register our service control handler:
  //
  sshStatusHandle =
      RegisterServiceCtrlHandler(service_name.c_str(), service_ctrl);

  if (!sshStatusHandle) {
    AddToMessageLog(MSG_FLAGS_ERROR, "RegisterServiceCtrlHandler failed");
    goto cleanup;
  }

  // SERVICE_STATUS members that don't change in example
  //
  ssStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
  ssStatus.dwServiceSpecificExitCode = 0;

  // report the status to the service control manager.
  //
  if (!::ReportStatusToSCMgr(SERVICE_RUNNING, // service state
                             NO_ERROR,        // exit code
                             3000)) {         // wait hint
    AddToMessageLog(MSG_FLAGS_ERROR, "ReportStatusToSCMgr failed");
    goto cleanup;
  }

  service_box_main((int)dwArgc, (const char **)lpszArgv);

cleanup:

  // try to report the stopped status to the service control manager.
  //
  if (sshStatusHandle) {
    (VOID) ReportStatusToSCMgr(SERVICE_STOPPED, dwErr, 0);
  }

  return;
}

/**
 * 安装服务到Windows服务管理器.
 *
 * \return true或false
 */
bool install_service_internal() {
  SC_HANDLE schService;
  SC_HANDLE schSCManager;
  TCHAR szPath[512] = {0};

  bool ret = true;

  if (GetModuleFileName(NULL, szPath, sizeof(szPath) - 1) == 0) {
    _tprintf(TEXT("Unable to install %s - %s\n"), service_name.c_str(),
             GetLastErrorText(szErr, 256));
    return false;
  }

  schSCManager = OpenSCManager(NULL, // machine (NULL == local)
                               NULL, // database (NULL == default)
                               SC_MANAGER_ALL_ACCESS // access required
  );
  if (schSCManager) {
    schService = CreateService(
        schSCManager,              // SCManager database
        service_name.c_str(),      // name of service
        service_name.c_str(),      // name to display
        SERVICE_ALL_ACCESS,        // desired access
        SERVICE_WIN32_OWN_PROCESS, // service type
        SERVICE_AUTO_START,   // start type -- alternative: SERVICE_AUTO_START
        SERVICE_ERROR_NORMAL, // error control type
        szPath,               // service's binary
        NULL,                 // no load ordering group
        NULL,                 // no tag identifier
        NULL,                 // TEXT(SZDEPENDENCIES),       // dependencies
        NULL,                 // LocalSystem account
        NULL);                // no password

    if (schService) {
      _tprintf(TEXT("%s installed.\n"), service_name.c_str());
      CloseServiceHandle(schService);
    } else {
      _tprintf(TEXT("CreateService failed - %s\n"),
               GetLastErrorText(szErr, sizeof(szErr) - 1));
      ret = false;
    }

    CloseServiceHandle(schSCManager);
  } else {
    _tprintf(TEXT("OpenSCManager failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  }
  return ret;
}

/**
 * 检查是否是Windows服务.
 *
 * \return true或false
 */
bool check_service() {
  SC_HANDLE schService;
  SC_HANDLE schSCManager;
  schSCManager = OpenSCManager(NULL, // machine (NULL == local)
                               NULL, // database (NULL == default)
                               SC_MANAGER_CONNECT); // access required
  if (schSCManager) {
    schService = OpenService(schSCManager, service_name.c_str(),
                             DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);

    if (schService) {
      CloseServiceHandle(schService);
      CloseServiceHandle(schSCManager);
      return true;
    }
  }
  if (schSCManager) {
    CloseServiceHandle(schSCManager);
  }
  return false;
}

/**
 * 卸载Windows服务.
 *
 * \return true或false
 */
bool uninstall_service_internal() {
  SC_HANDLE schService;
  SC_HANDLE schSCManager;

  bool ret = true;

  schSCManager = OpenSCManager(NULL,              // machine (NULL == local)
                               NULL,              // database (NULL == default)
                               SC_MANAGER_CONNECT // access required
  );
  if (schSCManager) {
    schService = OpenService(schSCManager, service_name.c_str(),
                             DELETE | SERVICE_STOP | SERVICE_QUERY_STATUS);

    if (schService) {
      // try to stop the service
      if (ControlService(schService, SERVICE_CONTROL_STOP, &ssStatus)) {
        _tprintf(TEXT("Stopping %s."), service_name.c_str());
        Sleep(1000);

        while (QueryServiceStatus(schService, &ssStatus)) {
          if (ssStatus.dwCurrentState == SERVICE_STOP_PENDING) {
            _tprintf(TEXT("."));
            Sleep(1000);
          } else
            break;
        }

        if (ssStatus.dwCurrentState == SERVICE_STOPPED)
          _tprintf(TEXT("\n%s stopped.\n"), service_name.c_str());
        else {
          _tprintf(TEXT("\n%s failed to stop.\n"), service_name.c_str());
          ret = false;
        }
      }

      // now remove the service
      if (DeleteService(schService)) {
        _tprintf(TEXT("%s removed.\n"), service_name.c_str());
      } else {
        _tprintf(TEXT("DeleteService failed - %s\n"),
                 GetLastErrorText(szErr, 256));
        ret = false;
      }

      CloseServiceHandle(schService);
    } else {
      _tprintf(TEXT("OpenService failed - %s\n"),
               GetLastErrorText(szErr, sizeof(szErr) - 1));
      ret = false;
    }

    CloseServiceHandle(schSCManager);
  } else {
    _tprintf(TEXT("OpenSCManager failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  }
  return ret;
}

//
//  FUNCTION: service_ctrl
//
//  PURPOSE: This function is called by the SCM whenever
//           ControlService() is called on this service.
//
//  PARAMETERS:
//    dwCtrlCode - type of control requested
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
VOID WINAPI service_ctrl(DWORD dwCtrlCode) {
  // Handle the requested control code.
  //
  switch (dwCtrlCode) {
  // Stop the service.
  //
  // SERVICE_STOP_PENDING should be reported before
  // setting the Stop Event - hServerStopEvent - in
  // ServiceStop().  This avoids a race condition
  // which may result in a 1053 - The Service did not respond...
  // error.
  case SERVICE_CONTROL_STOP:
    ReportStatusToSCMgr(SERVICE_STOP_PENDING, NO_ERROR, 0);
    // TODO
    close_all_box();
    return;

    // Update the service status.
    //
  case SERVICE_CONTROL_INTERROGATE:
    break;

    // invalid control code
    //
  default:
    break;
  }

  ReportStatusToSCMgr(ssStatus.dwCurrentState, NO_ERROR, 0);
}

//
//  FUNCTION: CmdStartService()
//
//  PURPOSE: Start the service
//
//  PARAMETERS:
//    none
//
//  RETURN VALUE:
//    0 if success
//
//  COMMENTS:

bool start_service() {
  bool ret = true;

  SC_HANDLE schSCManager;
  SC_HANDLE schService;

  // Open a handle to the SC Manager database.
  schSCManager = OpenSCManager(NULL, // local machine
                               NULL, // ServicesActive database
                               SC_MANAGER_ALL_ACCESS); // full access rights

  if (NULL == schSCManager) {
    _tprintf(TEXT("OpenSCManager failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  }

  schService = OpenService(schSCManager,         // SCM database
                           service_name.c_str(), // service name
                           SERVICE_ALL_ACCESS);

  if (schService == NULL) {
    _tprintf(TEXT("OpenService failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  }
  if (!StartService(schService, // handle to service
                    0,          // number of arguments
                    nullptr))   // arguments
  {
    _tprintf(TEXT("StartService failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  } else {
    _tprintf(TEXT("Service Started\n"));
  }
  CloseServiceHandle(schService);
  CloseServiceHandle(schSCManager);
  return ret;
}

/**
 * 停止Windows服务.
 *
 * \return true或false
 */
bool stop_service() {
  bool ret = true;

  SC_HANDLE schSCManager;
  SC_HANDLE schService;

  SERVICE_STATUS ServiceStatus;

  // Open a handle to the SC Manager database.
  schSCManager = OpenSCManager(NULL, // local machine
                               NULL, // ServicesActive database
                               SC_MANAGER_ALL_ACCESS); // full access rights

  if (NULL == schSCManager) {
    _tprintf(TEXT("OpenSCManager failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  }

  schService = OpenService(schSCManager,         // SCM database
                           service_name.c_str(), // service name
                           SERVICE_ALL_ACCESS);

  if (schService == NULL) {
    _tprintf(TEXT("OpenService failed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  }

  if (!ControlService(schService,           // handle to service
                      SERVICE_CONTROL_STOP, // number of arguments
                      &ServiceStatus)) {
    _tprintf(TEXT("ControlServicefailed - %s\n"),
             GetLastErrorText(szErr, sizeof(szErr) - 1));
    ret = false;
  } else {
    _tprintf(TEXT("Service Stopped\n"));
  }
  CloseServiceHandle(schService);
  CloseServiceHandle(schSCManager);
  return ret;
}

//
//  FUNCTION: ReportStatusToSCMgr()
//
//  PURPOSE: Sets the current status of the service and
//           reports it to the Service Control Manager
//
//  PARAMETERS:
//    dwCurrentState - the state of the service
//    dwWin32ExitCode - error code to report
//    dwWaitHint - worst case estimate to next checkpoint
//
//  RETURN VALUE:
//    TRUE  - success
//    FALSE - failure
//
//  COMMENTS:
//
BOOL ReportStatusToSCMgr(DWORD dwCurrentState, DWORD dwWin32ExitCode,
                         DWORD dwWaitHint) {
  static DWORD dwCheckPoint = 1;
  BOOL fResult = TRUE;

  if (dwCurrentState == SERVICE_START_PENDING) {
    ssStatus.dwControlsAccepted = 0;
  } else {
    ssStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
  }

  ssStatus.dwCurrentState = dwCurrentState;
  ssStatus.dwWin32ExitCode = dwWin32ExitCode;
  ssStatus.dwWaitHint = dwWaitHint;

  if ((dwCurrentState == SERVICE_RUNNING) ||
      (dwCurrentState == SERVICE_STOPPED)) {
    ssStatus.dwCheckPoint = 0;
  } else {
    ssStatus.dwCheckPoint = dwCheckPoint++;
  }

  // Report the status of the service to the service control manager.
  //
  if (!(fResult = SetServiceStatus(sshStatusHandle, &ssStatus))) {
    AddToMessageLog(MSG_FLAGS_ERROR, TEXT("SetServiceStatus"));
  }
  return fResult;
}

//
//  FUNCTION: AddToMessageLog(LPCTSTR lpszMsg)
//
//  PURPOSE: Allows any thread to log an error message
//
//  PARAMETERS:
//    lpszMsg - text for message
//
//  RETURN VALUE:
//    none
//
//  COMMENTS:
//
void AddToMessageLog(DWORD flags, LPCTSTR lpszMsg) {
  char szMsg[512] = {0};
  HANDLE hEventSource;
  LPCSTR lpszStrings[2];

  if (flags & MSG_FLAGS_SYS_CODE) {
    dwErr = GetLastError();
  } else {
    dwErr = 0;
  }

  _stprintf(szMsg, TEXT("%s error: %d"), service_name.c_str(), (int)dwErr);
  lpszStrings[0] = szMsg;
  lpszStrings[1] = lpszMsg;

  // Use event logging to log the error.
  //
  hEventSource = RegisterEventSource(NULL, service_name.c_str());

  if (hEventSource != NULL) {
    ReportEvent(hEventSource, // handle of event source
                              // event type
                (flags & MSG_FLAGS_ERROR) ? EVENTLOG_ERROR_TYPE
                                          : EVENTLOG_INFORMATION_TYPE,
                0,           // event category
                0,           // event ID
                NULL,        // current user's SID
                2,           // strings in lpszStrings
                0,           // no bytes of raw data
                lpszStrings, // array of error strings
                NULL);       // no raw data

    (VOID) DeregisterEventSource(hEventSource);
  }
}

void syslog(WORD flags, const char* msg) {
  HANDLE hEventSource;
  LPCSTR lpszStrings[1];

  lpszStrings[0] = msg;

  // Use event logging to log the error.
  //
  hEventSource = RegisterEventSource(NULL, service_name.c_str());

  if (hEventSource != NULL) {
    ReportEvent(hEventSource, // handle of event source
                              // event type
                flags,       // log event flag
                0,           // event category
                0,           // event ID
                NULL,        // current user's SID
                1,           // strings in lpszStrings
                0,           // no bytes of raw data
                lpszStrings, // array of error strings
                NULL);       // no raw data

    (VOID) DeregisterEventSource(hEventSource);
  }
}

//
//  FUNCTION: GetLastErrorText
//
//  PURPOSE: copies error message text to string
//
//  PARAMETERS:
//    lpszBuf - destination buffer
//    dwSize - size of buffer
//
//  RETURN VALUE:
//    destination buffer
//
//  COMMENTS:
//
LPTSTR GetLastErrorText(LPTSTR lpszBuf, DWORD dwSize) {
  DWORD dwRet;
  LPTSTR lpszTemp = NULL;

  dwRet = FormatMessage(
      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
          FORMAT_MESSAGE_ARGUMENT_ARRAY,
      NULL, GetLastError(), LANG_NEUTRAL, (LPTSTR)&lpszTemp, 0, NULL);

  // supplied buffer is not long enough
  if (!dwRet || ((long)dwSize < (long)dwRet + 14)) {
    lpszBuf[0] = TEXT('\0');
  } else {
    lpszTemp[lstrlen(lpszTemp) - 2] =
        TEXT('\0'); // remove cr and newline character
    _stprintf(lpszBuf, TEXT("%s (0x%x)"), lpszTemp, (int)GetLastError());
  }

  if (lpszTemp) {
    LocalFree((HLOCAL)lpszTemp);
  }

  return lpszBuf;
}

#endif // WIN32
